package movEat.Kernel.Import;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import java.util.zip.ZipInputStream;

import movEat.Definitions.Genre;
import movEat.Definitions.MovieDB;
import movEat.Definitions.Rating;
import movEat.Definitions.TVshowDB;
import movEat.Generic.Error;
import movEat.Kernel.Kernel;


/**
 * ImdbParser can parse the movies, actors, actresses, ratings, genres, mpaa-ratings-reasons,
 * countries, keywords lists from the imdb text data (http://www.imdb.com/interfaces).
 */
public class ImdbParser
{
    private static final String MOVIES_MARKER = "MOVIES LIST";
    private static final int MOVIES_SKIPS = 2;
    private static final String ACTRESS_MARKER = "THE ACTRESSES LIST";
    private static final int ACTRESS_SKIPS = 4;
    private static final String ACTOR_MARKER = "THE ACTORS LIST";
    private static final int ACTOR_SKIPS = 4;
    private static final String STARS_MARKER = "MOVIE RATINGS REPORT";
    private static final int STARS_SKIPS = 2;
    private static final String COUNTRY_MARKER ="COUNTRIES LIST";
    private static final int COUNTRY_SKIPS = 1;
    private static final String GENRE_MARKER ="8: THE GENRES LIST";
    private static final int GENRE_SKIPS = 2;
    private static final String RATING_MARKER ="MPAA RATINGS REASONS LIST";
    private static final int RATING_SKIPS = 2;
    private static final String RATING_MOVIE_PREFIX ="MV:";
    private static final int RATING_MOVIE_OFFSET =4;
    private static final String RATING_RESON_PREFIX ="RE:";
    private static final String KW_MARKER ="8: THE KEYWORDS LIST";
    private static final int KW_SKIPS = 2;

	/**
     * Parsers the tab-separated movie list file, each line containing a movie
     * title and the year the movie was released.
     * @returns a map of movie names to MovieDB objects.
     * @param file: name of movie list file.
     * The file can be .gz or .zip compressed, and must then have the corresponding file extension.
     * @throws IOException- if unable to open the movie list file
     */
    
    public Map<String,MovieDB> parseMoviesTable( final String file ) throws IOException
    {
    	
        if ( file == null ){
            throw new IllegalArgumentException( "Null movie file" );
        }
    	Map<String,MovieDB> movieMap= new HashMap<String,MovieDB>();
        BufferedReader fileReader = getFileReader( file, MOVIES_MARKER ,MOVIES_SKIPS );
        String line = fileReader.readLine();
        while (line!=null && !line.contains("------------------")){
            // get rid of blank lines, TV shows, videos and video games
            if ( "".equals( line ) || Utils.notAMovie(line) ){
                line = fileReader.readLine();
                continue;
            }
            final int yearSep = line.indexOf( '\t' );
            
            if ( yearSep > 0 ){
            	final String title = line.substring( 0, yearSep ).trim();
                String yearString = line.substring( yearSep ).trim();
                if ( yearString.length() > 4 ){
                    yearString = yearString.substring( 0, 4 );
                }
                // get rid of non valid years
                if ( yearString.length() == 0 || yearString.charAt( 0 ) == '?'){
                    line = fileReader.readLine();
                    continue;
                }
                //get rid of titles that contain non ascii chars
                if (!Utils.isValidAscii(title)){
                	line = fileReader.readLine();
                	continue;
                }
                final int year = Integer.parseInt( yearString );
                MovieDB movie=new MovieDB();
                movie.setYear(year);
                movie.setImdbName(title);
                //giving a movie a defult R-Rating
                movie.setRating(Rating.KIDS);
                movie.setIsDrink(false);
                movieMap.put(title, movie);
            }
            line = fileReader.readLine();
        }
        fileReader.close();
        return movieMap;
    }
    
    
	/**
     * Parsers the tab-separated movie list file for series, each line containing a series
     * title, episode and the year the movie was released.
     * @returns a map of series names to MovieDB objects.
     * @param file: name of movie list file.
     * The file can be .gz or .zip compressed, and must then have the corresponding file extension.
     * @throws IOException- if unable to open the movie list file
     */
    
    public Map<String, TVshowDB> parseSeriesTable( final String file ) throws IOException{

        if ( file == null ){
            throw new IllegalArgumentException( "Null movie file" );
        }
    	Map<String, TVshowDB> seriesMap= new HashMap<String, TVshowDB>();
        BufferedReader fileReader = getFileReader( file, MOVIES_MARKER ,MOVIES_SKIPS );
        String line = fileReader.readLine();
        String currentSeries= null;
        // reaching the series
        while (line!= null && line.charAt(0)!='"'){
        	line=fileReader.readLine();
        }
        // reading until the end of the series
        while (line!=null && line.charAt(0)=='"' && !line.contains("------------------")){
            // get rid of blank lines
            if ( "".equals( line )){
                line = fileReader.readLine();
                continue;
            }
            final int yearSep1 = line.indexOf('"', 1);
            if ( yearSep1 > 0 ){
                final String title = line.substring( 1, yearSep1 ).trim();
                //avoiding duplications
                if (currentSeries!=null && currentSeries.equals(title)){
                    line = fileReader.readLine();
                    continue;
                }
                final int yearSep2 = line.indexOf('(', yearSep1+1);
                if (yearSep2 > yearSep1){
                	final int episodeSep = line.indexOf(')', yearSep2 + 1);
                	if (episodeSep > yearSep2){
                		String yearString = line.substring( yearSep2 + 1, episodeSep).trim();
                        if ( yearString.length() > 4 ){
                            yearString = yearString.substring( 0, 4 );
                        }
                        // get rid of non valid years
                        if ( yearString.length() == 0 || yearString.charAt( 0 ) == '?'){
                            line = fileReader.readLine();
                            continue;
                        }
                        //get rid of titles that contain non ascii chars
                        if (!Utils.isValidAscii(title)){
                        	line = fileReader.readLine();
                        	continue;
                        }
                        final int year = Integer.parseInt( yearString );
                        TVshowDB series=new TVshowDB();
                        series.setDecade(Utils.getDecade(year));
                        series.setImdbName(title);
                        seriesMap.put(title, series);
                        currentSeries = title;
                	}
                }               
            }
            line = fileReader.readLine();
        }
        fileReader.close();
        return seriesMap;
    }

    /**
     * Parsers the tab-separated actors list file. A line begins with actor name
     * then followed by a tab and a movie title the actor acted in. Additional
     * movies the current actor acted in are found on the following line that
     * starts with a tab followed by the movie title.
     * @return: List of ActorDB (according to the input). 
     * 			moviesMap is updated- actors are added to movieDB of the corresponding movie  
     * @pre: the movie list was parsed before (the method makes sure each movie already appears in the movie map)
     * @param actorFile, moviesMap
     * 			actresses=true iff the input is actresses file
     * @throws IOException- if unable to open actor list file
     */
    
    public void parseActorsTable( final String actorFile, Map<String,MovieDB> moviesMap, List<String> actors 
    		,boolean actresses) throws IOException{
    	
        if ( actorFile == null ){
        	if (actresses)
        		throw new IllegalArgumentException( "Null actresses file" );
        	else
        		throw new IllegalArgumentException( "Null actors file" );
        }
        BufferedReader fileReader;
        if (actresses)
        	fileReader = getFileReader(actorFile, ACTRESS_MARKER, ACTRESS_SKIPS);
        else
        	fileReader = getFileReader( actorFile, ACTOR_MARKER, ACTOR_SKIPS);
        parseActorItems( fileReader, actors, moviesMap);
        fileReader.close();
    }
    
    /** 
     * @param fileReader
     * @param actors
     * @param movies
     * @throws IOException
     */
    
    private void parseActorItems( BufferedReader fileReader, List<String> actors, 
    		Map<String,MovieDB> movies) throws IOException
    {
        String line = fileReader.readLine();
        String currentActor = null;
        boolean skipFilms=false;
        //Maintaining the serial numbers of the actors 
        int actorCount=actors.size()-1;
        while ( (line != null) && !line.contains("---------------")){
            // get rid of blank lines
            if ( "".equals( line ) ){
                line = fileReader.readLine();
                continue;
            }
            int actorSep = line.indexOf( '\t' );
            if ( actorSep >= 0 ){
                String actor = line.substring( 0, actorSep ).trim();
                // actor's name is non valid (ascii wise)
                if (!Utils.isValidAscii(actor)){
                	skipFilms=true;
                	line= fileReader.readLine();
                	continue;
                }
                //case of new actor
                if ( !"".equals( actor ) ){
                	//case that we already gained movies for the former actor
                	if (currentActor==null){
                		currentActor = actor;
                    	actorCount++;
                        skipFilms=false;
                	}
                	//we add a new actor only in case it doesn't equal the former's 
                	//name (in lower case), other wise we treat them the as same
                	else if (!currentActor.toLowerCase().equals(actor.toLowerCase())){
                		actors.add(currentActor);
                    	currentActor = actor;
                    	actorCount++;
                        skipFilms=false;
                	}
                }
                //case the current actor's name is valid
                if (!skipFilms){
                	String title = line.substring( actorSep ).trim();
                    //ignore series, tv shows, video games and movies with unknown year
                    if ( title.length() == 0 || Utils.notAMovie(title)){
                        line = fileReader.readLine();
                        continue;
                    }
                    int characterStart = title.indexOf( '[' );
                    int creditStart = title.indexOf( '<' );
                    if ( characterStart > 0 ){
                        title = title.substring( 0, characterStart ).trim();
                    }
                    else if ( creditStart > 0 ){
                        title = title.substring( 0, creditStart ).trim();
                    }
                    int spaces = title.indexOf( "  " );
                    if ( spaces > 0 ){
                        if ( title.charAt( spaces - 1 ) == ')'
                            && title.charAt( spaces + 2 ) == '(' ){
                            title = title.substring( 0, spaces ).trim();
                        }
                    }
                    //making sure title is a valid ascii name
                    if (!Utils.isValidAscii(title)){
                        line = fileReader.readLine();
                        continue;
                    }
                    MovieDB moviePerTitle=movies.get(title);
                    if (moviePerTitle != null){
                    	List<Integer> actorsPerMovie = moviePerTitle.getActor_nums();
                    	if (actorsPerMovie == null){
                    		actorsPerMovie = new ArrayList<Integer>();
                    		actorsPerMovie.add(actorCount);
                    		moviePerTitle.setActor_nums(actorsPerMovie);
                    	}
                    	else{
                    		if(actorCount!=actorsPerMovie.get(actorsPerMovie.size()-1))
                    			actorsPerMovie.add(actorCount);
                    	}
                    }
                }
            }
            line = fileReader.readLine();
        }
        //inserting the last actor
        if (actorCount > 0 ){
        	actors.add(currentActor);
        }
    }

    /**
     * ratings list format:
     * [*(new)] (distribution) (votes) (rating) (name)
     * @returns moviesMap is updated- rating is added to movieDB of the corresponding movie  
     * @pre: the movie.list file was parsed before (the method makes sure each movie already appears in the movie map)
     * @throws IOException 
     * 
     */
    public void parseStarsTable(final String starsFile, Map<String,MovieDB> moviesMap) throws IOException{
    	if ( starsFile == null )
        {
            throw new IllegalArgumentException( "Null rating file" );
        }
        BufferedReader fileReader = getFileReader( starsFile, STARS_MARKER ,STARS_SKIPS );
        String line = fileReader.readLine();
        while ( (line!=null) && !line.contains("------------------")){
        	if (line.isEmpty()){
        		line= fileReader.readLine();
        		continue;        		
        	}
        	String[] brokenLine=line.split(" {2} *");
        	//there aren't all of the properties
        	if (brokenLine.length<5){
        		line= fileReader.readLine();
        		continue;
        	}
        	String title= brokenLine[4].trim();
        	if (title.length()==0 || Utils.notAMovie(title)){
        		line= fileReader.readLine();
        		continue;        		
        	}
        	double stars;
        	try{
        		stars= Double.valueOf(brokenLine[3]);
        		MovieDB movie=moviesMap.get(title);
            	if (movie != null){
            		movie.setStars(stars);
            	}
            	line=fileReader.readLine();
        	}
        	catch(NumberFormatException exception){
        		line= fileReader.readLine();
        		continue;
        	}     	
        }
        fileReader.close();
    }

    /**
     * ratings list format:
     * [*(new)] (distribution) (votes) (rating) (name)
     * @returns moviesMap is updated- rating is added to movieDB of the corresponding movie  
     * @pre: the movie.list file was parsed before (the method makes sure each movie already appears in the movie map)
     * @throws IOException 
     * 
     */
    public void parseStarsSeriesTable(final String starsFile, Map<String,TVshowDB> seriesMap) throws IOException{
    	if ( starsFile == null )
        {
            throw new IllegalArgumentException( "Null rating file" );
        }
        BufferedReader fileReader = getFileReader( starsFile, STARS_MARKER ,STARS_SKIPS );
        String line = fileReader.readLine();
        boolean reachedSeries=false;
        String currentSeries=null;
        while ( (line!=null) && !line.contains("------------------")){
        	if (line.isEmpty()){
        		line= fileReader.readLine();
        		continue;        		
        	}
        	String[] brokenLine=line.split(" {2} *");
        	//there aren't all of the properties
        	if (brokenLine.length<5){
        		line= fileReader.readLine();
        		continue;
        	}
        	
        	String title= brokenLine[4].trim();
        	if (title.length()==0){
        		line= fileReader.readLine();
        		continue;        		
        	}
        	if (title.charAt(0)!='"'){
            	//done with the series sequence
        		if (reachedSeries)
        			break;
            	line= fileReader.readLine();
            	continue;          			
        	}
			//first time we encounter a series
        	if (title.charAt(0) == '"' && !reachedSeries){
        		reachedSeries=true;
        	}
        	final int yearSep1 = title.indexOf('"', 1);
            if ( yearSep1 <= 0 ){
            	line= fileReader.readLine();
            	continue; 
            }
            title = title.substring( 1, yearSep1 ).trim();
            //avoiding duplications
            if (currentSeries!=null && currentSeries.equals(title)){
                line = fileReader.readLine();
                continue;
            }
            if (!Utils.isValidAscii(title)){
            	line = fileReader.readLine();
            	continue;
            }
            currentSeries=title;
        	double stars;
        	try{
        		stars= Double.valueOf(brokenLine[3]);
        		TVshowDB series=seriesMap.get(title);
            	if (series != null){
            		series.setStars((int)(stars*10));
            	}
            	line=fileReader.readLine();
        	}
        	catch(NumberFormatException exception){
        		line= fileReader.readLine();
        		continue;
        	}     	
        }
        fileReader.close();
    }
    
    
    /**
     * Parsers the tab-separated country.list file, each line containing a movie-title</t>country
     * there might be multiple countries per movie.
     * @returns moviesMap is updated- country is added to movieDB of the corresponding movie  
     * @pre: the movie list was parsed before (the method makes sure each movie already appears in the movie map)
     * @throws IOException- if unable to open the country.list file
     */
    public void parseCountryTable( final String file ,Map<String,MovieDB> movieMap) throws IOException{
    	
        if ( file == null )
        {
            throw new IllegalArgumentException( "Null country file" );
        }
        BufferedReader fileReader = getFileReader( file, COUNTRY_MARKER ,COUNTRY_SKIPS );
        String line = fileReader.readLine();
        while (line!=null && !line.contains("------------------")){
            // get rid of blank lines, TV shows, videos and video games
            if ( "".equals( line ) || Utils.notAMovie(line) ){
                line = fileReader.readLine();
                continue;
            }
            final int countrySep = line.indexOf( '\t' );
            if ( countrySep > 0 ){
            	final String title = line.substring( 0, countrySep ).trim();
                String country = line.substring( countrySep ).trim();
                // get rid of non valid countries
                if ( country.length() == 0){
                    line = fileReader.readLine();
                    continue;
                }
                //get rid of titles that contain non ascii chars
                if (!Utils.isValidAscii(title)){
                	line = fileReader.readLine();
                	continue;
                }
                MovieDB movie=movieMap.get(title);
            	if (movie != null){
            		if (movie.getCountry()== null)
            			movie.setCountry(country);
            	}
            }
            line = fileReader.readLine();
        }
        fileReader.close();
    }
    
    /**
     * Parsers the tab-separated country list file, each line containing a movie-title</t>genre
     * there might be multiple genres per movie.
     * @returns moviesMap is updated- category is added to movieDB of the corresponding movie  
     * @pre: the movie list was parsed before (the method makes sure each movie already appears in the movie map)
     * @throws IOException- if unable to open the country list file
     */
    public void parseGenreTable( final String file ,Map<String,MovieDB> movieMap) throws IOException{
    	
        if ( file == null )
        {
            throw new IllegalArgumentException( "Null genre file" );
        }
        BufferedReader fileReader = getFileReader( file, GENRE_MARKER ,GENRE_SKIPS );
        String line = fileReader.readLine();
        while (line!=null && !line.contains("------------------")){
            // get rid of blank lines, TV shows, videos and video games
            if ( "".equals( line ) || Utils.notAMovie(line) ){
                line = fileReader.readLine();
                continue;
            }
            final int genreSep = line.indexOf( '\t' );
            if ( genreSep > 0 ){        	
                final String title = line.substring( 0, genreSep ).trim();
                String genre = line.substring( genreSep ).trim();
                // get rid of empty genres
                if ( genre.length() == 0){
                    line = fileReader.readLine();
                    continue;
                }
                //get rid of titles that contain non ascii chars
                if (!Utils.isValidAscii(title)){
                	line = fileReader.readLine();
                	continue;
                }
                MovieDB movie=movieMap.get(title);
            	if (movie != null){
            		Genre calculatedGenre= Utils.getGenre(genre);
            		if (calculatedGenre!=null){
                		List<Genre> movieGenres= movie.getGenres();
                		if (movieGenres==null){
                			movieGenres= new ArrayList<Genre>();
                			movieGenres.add(calculatedGenre);
                			movie.setGenres(movieGenres);
                		}
                		else{
                			if (!movieGenres.get(movieGenres.size()-1).equals(calculatedGenre))
                				movieGenres.add(calculatedGenre);                			
                		}
            		}            		
            	}
            }
            line = fileReader.readLine();
        }
        fileReader.close();
    }
    
    /** 
     * Parsers the mpaa file. structure:
     * 		MV: movie-title
     * 		RE: rated ___ for ... (this row might appear several times)
     * 		------------------------------------ 
     * @pre: the movie list was parsed before (the method makes sure each movie already appears in the movie map)
     * @returns moviesMap is updated- mpaa rating is added to movieDB of the corresponding movie- 
     * classified by KIDS,TEENS,ADULTS
     * @param file
     * @param movieMap
     * @throws IOException if unable to open the mpaa rating file
     */
    
    public void parseRatingTable( final String file ,Map<String,MovieDB> movieMap) throws IOException{	
        if ( file == null ){
            throw new IllegalArgumentException( "Null mpaa rating file" );
        }
        BufferedReader fileReader = getFileReader( file, RATING_MARKER ,RATING_SKIPS );
        String line = fileReader.readLine();
        String title;
        Rating rating;
        MovieDB movie;
        while (line!=null){
        	// get rid of blank and separating lines
            if ( "".equals( line ) || line.contains("---------------") ){
                line = fileReader.readLine();
                continue;
            }
            // encountering a new record
            if (line.startsWith(RATING_MOVIE_PREFIX)){
            	title=line.substring(RATING_MOVIE_OFFSET).trim();
            	if (Utils.notAMovie(title)||!Utils.isValidAscii(title)){
                    line = fileReader.readLine();
                    continue;
            	}
            	//read reason
            	line=fileReader.readLine();
            	if (line.startsWith(RATING_RESON_PREFIX)){
            		rating=Utils.getRating(line);
            		if (rating!=null){
            			movie=movieMap.get(title);
            			if (movie!=null){
            				movie.setRating(rating);
            			}
            		}
            	}
            }
            line=fileReader.readLine();
        }
        fileReader.close();
    }

    /**
     * Parsers the tab-separated keyword.list file, each line containing a movie-title</t>keyword
     * there might be multiple keyword per movie.
     * @returns moviesMap is updated- isDrink of the corresponding MovieDB is modified to #t
     * if the keyword appears in the list of keyWords  
     * @pre: the movie list was parsed before (the method makes sure each movie already appears in the movieMap)
     * @throws IOException- if unable to open the keywords.list file
     */
    
    public void parseKWTable( final String file ,Map<String,MovieDB> movieMap, List<String> keywords) 
    throws IOException{
    	
        if ( file == null )
        {
            throw new IllegalArgumentException( "Null keywords file" );
        }
        BufferedReader fileReader = getFileReader( file, KW_MARKER ,KW_SKIPS );
        String line = fileReader.readLine();
        while (line!=null && !line.contains("------------------")){
            // get rid of blank lines, TV shows, videos and video games
            if ( "".equals( line ) || Utils.notAMovie(line) ){
                line = fileReader.readLine();
                continue;
            }
            final int kwSep = line.indexOf( '\t' );
            if ( kwSep > 0 ){        	
                final String title = line.substring( 0, kwSep ).trim();
                String kw = line.substring( kwSep ).trim();
                // get rid of non valid keywords
                if ( kw.length() == 0 || !keywords.contains(kw)){
                    line = fileReader.readLine();
                    continue;
                }
                //get rid of titles that contain non ascii chars
                if (!Utils.isValidAscii(title)){
                	line = fileReader.readLine();
                	continue;
                }
                MovieDB movie=movieMap.get(title);
            	if (movie != null){
            		movie.setIsDrink(true);
            	}
            }
            line = fileReader.readLine();
        }
        fileReader.close();
    }
    
    
    /**
     * Get file reader that corresponds to file extension.
     * @return a file reader that uncompresses data if needed
     * @throws IOException
     */
    
    private BufferedReader getFileReader( final String file, String pattern,
        int skipLines ) throws IOException{
			BufferedReader fileReader;
		    // support compressed files
		    if ( file.endsWith( ".gz" ) ){
		        fileReader = new BufferedReader( new InputStreamReader(
		            new GZIPInputStream( new FileInputStream( file ) ) ) );
		    }
		    else if ( file.endsWith( ".zip" ) ){
		        fileReader = new BufferedReader( new InputStreamReader(
		            new ZipInputStream( new FileInputStream( file ) ) ) );
		    }
		    else{
		        fileReader = new BufferedReader( new FileReader( file ) );
		    }
		    String line = "";
		    while ( !pattern.equals( line ) ){
		        line = fileReader.readLine();
		    }
		    for ( int i = 0; i < skipLines; i++ ){
		        line = fileReader.readLine();
		    }
		    return fileReader;
    }
    
}